import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Holds the DVDRecord's used by the DVD System Back-end. Transactions occur on
 * this structure
 * 
 * @author Adam Contois, Adam Domurad, Pat Goebel
 */
public class DVDInventory {
	/* Maps DVD names to DVD records */
	private Map<String, DVDRecord> records;
	/* Largest ID assigned to a DVD */
	private int maximumID;

	/**
	 * Initialize a DVD database with a list of records. The list is used to
	 * create a mapping of DVD name -> record
	 */
	public DVDInventory(List<DVDRecord> records) {
		maximumID = 0;
		this.records = new HashMap<String, DVDRecord>();
		for (DVDRecord dvd : records) {
			maximumID = Math.max(dvd.getID(), maximumID);
			this.records.put(dvd.getName(), dvd);
		}
	}

	/**
	 * Create a new DVDInventory initialized with a Master DVD File
	 * 
	 * @param masterfile
	 *            the master DVD file name
	 * @return the initialized DVDInventory
	 * @throws IOException
	 */
	static public DVDInventory createFromMasterDVDFile(String masterfile)
			throws IOException {
		BufferedReader br = new BufferedReader(new FileReader(masterfile));
		List<DVDRecord> records = new ArrayList<DVDRecord>();
		String s;
		while ((s = br.readLine()) != null) {
			System.out.println(s.length());
			DVDRecord newRecord = DVDRecord.createRecordFromLine(s);
			if (newRecord == null)
				return null;
			records.add(newRecord);
		}
		return new DVDInventory(records);

	}

	/**
	 * Remove DVD record from the database
	 * 
	 * @param dvdname
	 *            the DVD name to remove
	 * @return whether an object was removed
	 */
	public boolean remove(String dvdname) {
		return records.remove(dvdname) != null;
	}

	/**
	 * Insert a DVD record into the inventory
	 * 
	 * @param record
	 *            Record to add
	 */
	public void addDVDRecord(DVDRecord record) {
		records.put(record.getName(), record);
	}

	/**
	 * Return DVD record object from the database
	 * 
	 * @param dvdname
	 *            the DVD name to fetch
	 * @return the DVD record corresponding to name
	 */
	public DVDRecord getDVDRecord(String dvdname) {
		DVDRecord e = records.get(dvdname);
		if (e == null)
			throw new InvalidRecord("The DVD '" + dvdname
					+ "' is not available.");
		return e;
	}

	/**
	 * Check if DVD exists in database
	 * 
	 * @param dvdname
	 *            the DVD name to check
	 * @return whether the record exists or not
	 */
	public boolean hasDVDRecord(String dvdname) {
		return records.containsKey(dvdname);
	}

	/**
	 * Create an output listing of the DVD's after transaction processing, for
	 * use in the front-end
	 * 
	 * @param file
	 *            the location to save the produced file
	 */
	public void toDVDFile(String file) throws IOException {
		// Create a new transaction file, overwriting any existing files
		PrintWriter fw = new PrintWriter(new FileWriter(file, false));
		for (DVDRecord record : records.values()) {
			fw.println(record.toDVDFileString());
		}
		fw.close();
	}

	/**
	 * Create the updated master DVD file for use in the back end
	 * 
	 * @param file
	 *            the location to save the produced file
	 */
	public void toMasterDVDFile(String file) throws IOException {
		// Create a new transaction file, overwriting any existing files
		PrintWriter fw = new PrintWriter(new FileWriter(file, false));
		List<DVDRecord> rec = new ArrayList<DVDRecord>(records.values());
		Collections.sort(rec, new Comparator<DVDRecord>(){
			@Override
			public int compare(DVDRecord a1, DVDRecord a2) {
				if (a1.getID() < a2.getID()) return -1;
				return +1;
			} });
		for (DVDRecord record : rec) {
			fw.println(record.toMasterDVDFileString());
		}
		fw.close();
	}

	/**
	 * Performs a transaction to the DVD records based on the transaction type
	 * 
	 * @param t
	 *            DVDTransaction object to switch on
	 */
	public void performTransaction(DVDTransaction t) {
		int transCode = t.getCode();

		switch (transCode) {
		case 00://log out
			break;
		case 01:// "rent"
			performRentTransaction(t);
			break;
		case 02:// "return"
			performReturnTransaction(t);
			break;
		case 03:// "create"
			performCreateTransaction(t);
			break;
		case 04:// "add"
			performAddTransaction(t);
			break;
		case 05:// "remove"
			performRemoveTransaction(t);
			break;
		case 06:// "buy"
			performBuyTransaction(t);
			break;
		case 07:// "sell"
			performSellTransaction(t);
			break;
		default:
			throw new InvalidFileFormat("Invalid transaction code!");
		}
	}

	/**
	 * Apply a Create transaction to the DVD records
	 * 
	 * @param transaction
	 *            DVDTransaction object to process
	 */
	private void performCreateTransaction(DVDTransaction transaction) {
		String name = transaction.getDVDName();
		int total = transaction.getNumberOfCopies();
		int remain = total;
		boolean rentalStatus = (transaction.getRentalStatus().equals("R"));
		int priceInCents = transaction.getPriceInCents();

		if (hasDVDRecord(name))
			throw new InvalidRecord("A DVD already exists with the title '"
					+ name + "'.");

		addDVDRecord(new DVDRecord(++maximumID, remain, total, rentalStatus,
				priceInCents, name));
	}

	/**
	 * Apply a Return transaction to the DVD records
	 * 
	 * @param t
	 *            DVDTransaction object to process
	 */
	private void performReturnTransaction(DVDTransaction t) {
		String name = t.getDVDName();
		int total = t.getNumberOfCopies();
		if (hasDVDRecord(name)) {
			DVDRecord dvd = getDVDRecord(name);
			int id = dvd.getID();
			int remaining = dvd.getRemaining();
			total += remaining;
			dvd.setRemaining(total);

		} else {
			boolean rentalStatus = false;
			int priceInCents = t.getPriceInCents();
			int remain = total;
			addDVDRecord(new DVDRecord(++maximumID, remain, total,
					rentalStatus, priceInCents, name));
		}
	}

	/**
	 * Apply a Rent transaction to the DVD records
	 * 
	 * @param t
	 *            DVDTransaction object to process
	 */
	private void performRentTransaction(DVDTransaction t) {
		// Throws exception if record does not exist
		DVDRecord record = getDVDRecord(t.getDVDName());
		if (!record.getRentalStatus())
			throw new InvalidRecord(
					"DVD can not be rented, it has been listed as for sale.");

		int copies = t.getNumberOfCopies();
		if (copies > record.getRemaining())
			throw new InvalidRecord("Insufficient copies of '"
					+ record.getName() + "' remaining.");
		if (copies <= 0)
			throw new InvalidFileFormat(
					"Invalid number of copies (less than or equal to 0).");

		int priceCents = copies * record.getPriceInCents();
		record.setRemaining(record.getRemaining() - copies);
	}

	/**
	 * Apply a Add transaction to the DVD records
	 * 
	 * @param t
	 *            DVDTransaction object to process
	 */
	private void performAddTransaction(DVDTransaction t) {
		String name = t.getDVDName();
		int total = t.getNumberOfCopies();
		DVDRecord dvd = getDVDRecord(name);
		int id = dvd.getID();
		int remaining = dvd.getRemaining();
		int dvdTotal = dvd.getTotal();
		dvdTotal += total;
		remaining += total;
		dvd.setRemaining(remaining);
		dvd.setTotal(dvdTotal);
	}

	/**
	 * Apply a Remove transaction to the DVD records
	 * 
	 * @param t
	 *            DVDTransaction object to process
	 */
	private void performRemoveTransaction(DVDTransaction t) {
		if (!hasDVDRecord(t.getDVDName()))
			throw new InvalidRecord("The DVD '" + t.getDVDName()
					+ "' is not available.");

		remove(t.getDVDName());
	}

	/**
	 * Apply a Buy transaction to the DVD records
	 * 
	 * @param t
	 *            DVDTransaction object to process
	 */
	private void performBuyTransaction(DVDTransaction t) {
		String name = t.getDVDName();
		int total = t.getNumberOfCopies();
		DVDRecord dvd = getDVDRecord(name);
		int dvdTotal = dvd.getTotal();
		int dvdRemain = dvd.getRemaining();
		dvdRemain -= total;
		dvdTotal -= total;
		if (dvdTotal > 0){
			dvd.setTotal(dvdTotal);
			dvd.setRemaining(dvdRemain);
		}
		else
			remove(name); // all copies sold
	}

	/**
	 * Apply a Sell transaction to the DVD records
	 * 
	 * @param t
	 *            DVDTransaction object to process
	 */
	private void performSellTransaction(DVDTransaction t) {
		DVDRecord record = getDVDRecord(t.getDVDName());
		int priceCents = t.getPriceInCents();
		record.setPriceInCents(priceCents);
		record.setRentalStatus(false);
	}
}
